#include <linux/module.h>
#include <linux/init.h>
#include <linux/gfp.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/vfs.h>
#include <linux/pagemap.h>
#include <linux/highmem.h>
#include <linux/string.h>
#include <linux/version.h>

#define YESNODUNNO_MAGIC 232535
#define LEVELS 5
static int nesting = 5;

module_param(nesting, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);

//--------------Working with fs-----------------------------------

static struct inode *yesnodunno_get_inode(struct super_block *, int);


static struct dentry *yesnodunno_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) /*struct nameidata *nd)*/
{
	/* TBD */
	struct inode *myinode;
	printk("dentry %s\nparent inode %lx nesting %i\n",dentry->d_name.name,dir->i_ino,*((int*)dir->i_private));
	if (strcmp(dentry->d_name.name,"yes") == 0 && nesting > *((int*)dir->i_private)) {
		myinode=yesnodunno_get_inode(dir->i_sb, (dir->i_ino) * 3 + 1);
		*((int*)myinode->i_private) = *((int*)dir->i_private) + 1;
		d_add(dentry,myinode);		
	}	

	if (strcmp(dentry->d_name.name,"no") == 0 && nesting > *((int*)dir->i_private)) {
		myinode=yesnodunno_get_inode(dir->i_sb, (dir->i_ino) * 3 + 2);
		*((int*)myinode->i_private) = *((int*)dir->i_private) + 1;
		d_add(dentry,myinode);
	}

	if (strcmp(dentry->d_name.name,"dunno") == 0 && nesting > *((int*)dir->i_private)) {
		myinode=yesnodunno_get_inode(dir->i_sb, (dir->i_ino) * 3 + 3);
		*((int*)myinode->i_private) = *((int*)dir->i_private) + 1;
		d_add(dentry,myinode);
	}
	printk("inode %lx nesting %i\n",dentry->d_inode->i_ino,*((int*)myinode->i_private));
        return NULL;
}


static const struct inode_operations yesnodunno_dir_inode_operations = {
	.lookup		= yesnodunno_lookup,
};

static int yesnodunno_readdir(struct file *filp, void *dirent, filldir_t filldir)
{
	/* TBD */
	printk("yesnodunno_readdir\n");
	int err;	
	struct dentry *de = filp->f_dentry;
	err = 2;
	if (filp->f_pos > 0 )
		err = 1;
	if (nesting > *((int *)de->d_inode->i_private)) {
		if (filldir(dirent, ".", 1, filp->f_pos++, de->d_inode->i_ino, DT_DIR)||(filldir(dirent, "..", 2, filp->f_pos++, de->d_parent->d_inode->i_ino, DT_DIR)))
			err = 0;
		if (filldir(dirent, "yes", 3, filp->f_pos++, (de->d_inode->i_ino) * 3 + 1, DT_REG ))
			err = 0;
		if (filldir(dirent, "no", 4, filp->f_pos++, (de->d_inode->i_ino) * 3 + 2, DT_REG ))
			err = 0;
		if (filldir(dirent, "dunno", 5, filp->f_pos++, (de->d_inode->i_ino) * 3 + 3, DT_REG ))
			err = 0;
	}
	return err;
}


static const struct file_operations yesnodunno_dir_operations = {
	.readdir = 	yesnodunno_readdir,
};


struct inode *yesnodunno_get_inode(struct super_block *sb, int num)
{
	struct inode *inode = new_inode(sb);

	if (inode) {
		inode->i_mode = S_IFDIR | 0555;
		inode->i_uid = 0;
		inode->i_gid = 0;
		inode->i_blocks = 0;
		inode->i_op = &yesnodunno_dir_inode_operations;
		inode->i_fop = &yesnodunno_dir_operations;
		inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
		inc_nlink(inode);
		inode->i_ino = num;
	}
	return inode;
}

static struct super_operations yesnodunno_ops = {
	.statfs 	= simple_statfs,
};

static int yesnodunno_fill_super(struct super_block *sb, void *data, int silent)
{
	struct inode *inode;
	struct dentry *root;

	sb->s_maxbytes = MAX_LFS_FILESIZE;
	sb->s_blocksize = PAGE_CACHE_SIZE;
	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
	sb->s_magic = YESNODUNNO_MAGIC;
	sb->s_op = &yesnodunno_ops;
	sb->s_time_gran = 1;
	inode = yesnodunno_get_inode(sb, 1);
	if (!inode)
		return -ENOMEM;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0))
	root = d_make_root(inode);
#else
	root = d_alloc_root(inode);
#endif
	if (!root) {
		iput(inode);
		return -ENOMEM;
	}
	sb->s_root = root;
	return 0;
}

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39))
static struct dentry *yesnodunno_mount(struct file_system_type *fs_type, int flags,const char *dev_name, void *data)
{
    return mount_bdev(fs_type, flags, dev_name, data, yesnodunno_fill_super);
}
#else
static int yesnodunno_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data, struct vfsmount *mnt)
{
	return get_sb_nodev(fs_type, flags, data, yesnodunno_fill_super, mnt);
}
#endif

static struct file_system_type yesnodunno_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "yesnodunno",
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39))
	.mount = yesnodunno_mount,
#else
	.get_sb = yesnodunno_get_sb,
#endif
	.kill_sb	= kill_anon_super,
};


//--------------Module init/exit------------------------------------

static int __init tri_fs_init(void)
{
	printk("tri_fs_init\n");
	return register_filesystem(&yesnodunno_fs_type);
}

static void __exit exit_tri_fs(void)
{
	unregister_filesystem(&yesnodunno_fs_type);
	printk("tri_fs_exit\n");
}

module_init(tri_fs_init)
module_exit(exit_tri_fs)

//--------------Information about module----------------------------

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anastasiya Solomina <sterlyadka333@mail.ru>");
